Skip to content

feat(login): optional single-use launch-token game boot#10421

Closed
Shuu-37 wants to merge 2 commits into
LandSandBoat:basefrom
phoenixffxi:feat/launch-token-upstream
Closed

feat(login): optional single-use launch-token game boot#10421
Shuu-37 wants to merge 2 commits into
LandSandBoat:basefrom
phoenixffxi:feat/launch-token-upstream

Conversation

@Shuu-37

@Shuu-37 Shuu-37 commented Jun 28, 2026

Copy link
Copy Markdown

I affirm:

  • I understand that if I do not agree to the following points by completing the checkboxes my PR will be ignored.
  • I understand I should leave resolving conversations to the LandSandBoat team so that reviewers won't miss what was said.
  • I have read and understood the Contributing Guide and the Code of Conduct.
  • I have tested my code and the things my code has changed since the last commit in the PR and will test after any later commits.

Summary

Adds an optional login_token rail to xi_connect's LOGIN_ATTEMPT, allowing a launcher to boot the game with a single-use, short-TTL token minted by an external auth service instead of a username/password. This supports launchers built around OAuth/SSO sign-in (e.g. "Login with Discord"), where the user has an authenticated app session but no game password to hand to xiloader.

It is built directly on top of the existing trust-token machinery already in base (isValidTrustTokenFormat / hashTrustToken, SHA-256-at-rest, 64-hex-char format), so it stays consistent with how trust tokens already work.

How it works

  • New game-DB table accounts_login_tokens (same shape as accounts_trust_tokens). Only the SHA-256 hash is stored; single-use and the ~5-minute TTL are enforced in code.
  • otpHelpers::validateAndConsumeLaunchToken() resolves accid + account status for a still-valid token, then DELETEs it — the caller whose delete affects exactly one row "wins" (single-use, replay-safe).
  • In LOGIN_ATTEMPT, when a login_token is present it replaces validatePassword and skips the OTP block — justified because the minting auth service already enforced authentication (incl. 2FA) before issuing the token. The existing username/password malformed checks are skipped on the token path (the token alone identifies the account).
  • The normal ban / ACCOUNT_STATUS_CODE::NORMAL check still runs on both paths — a token cannot boot a banned/non-normal account.
  • New result code LOGIN_ERROR_LAUNCH_TOKEN_INVALID = 0x14 for an expired/consumed/invalid token.

Unlike a trust token (which only bypasses the OTP second factor), a launch token stands in for both password and OTP at boot.

Backward compatibility

login_token is an optional JSON field. When absent, the password path is completely unchanged, so existing xiloaders work as-is — SupportedXiloaderVersion is not bumped.

Companion client change

A matching xiloader change parses login_token from --json, sends it in the 0x10 packet, and handles 0x14. Happy to open that PR against LandSandBoat/xiloader alongside this one if there's interest.

Notes for reviewers

  • We run this on our fork (Phoenix); opening upstream so it can live in base rather than only downstream.

Comment thread sql/accounts_login_tokens.sql Outdated
@@ -0,0 +1,21 @@
-- Single-use, short-TTL game-launch tokens.
--
-- Minted by the Phoenix auth service for an already-authenticated session

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — reworded to be backend-agnostic (dropped the implementation-specific references) and trimmed the comments throughout. The feature is generic: any external auth service can mint the token; we just happen to use it downstream.

@Shuu-37

Shuu-37 commented Jun 28, 2026

Copy link
Copy Markdown
Author

Companion client PR is now open: LandSandBoat/xiloader#50 (parses login_token from --json and sends it in the 0x10 login JSON, handles 0x14). The two are designed to land together.

@Shuu-37 Shuu-37 force-pushed the feat/launch-token-upstream branch from 11a1fcd to 6e2f908 Compare June 28, 2026 18:50
@Shuu-37 Shuu-37 requested a review from zach2good June 28, 2026 18:54
@LandSandBoat LandSandBoat deleted a comment from github-actions Bot Jun 28, 2026
@Xaver-DaRed Xaver-DaRed marked this pull request as draft June 28, 2026 19:51
Add an optional login_token to LOGIN_ATTEMPT so a launcher can boot using a
single-use, short-TTL token minted by an external auth service instead of a
password. When present, the token is resolved + consumed (single-use), the normal
ban/status check still runs, and both validatePassword and the OTP block are
skipped (the minting service already authenticated the session, incl. any 2FA).

- otp_helpers.h: validateAndConsumeLaunchToken() — resolve accid+status, then
  DELETE (rowsAffected==1 = single-use winner). Reuses trust-token format/hashing.
- auth_session.{h,cpp}: LOGIN_ERROR_LAUNCH_TOKEN_INVALID=0x14; parse login_token;
  skip username/password malformed checks on the token path.
- sql/accounts_login_tokens.sql: new table (mirrors accounts_trust_tokens).

Backward compatible: login_token is optional; the password path is unchanged, so
SupportedXiloaderVersion is not bumped.
@Shuu-37 Shuu-37 force-pushed the feat/launch-token-upstream branch from 6e2f908 to eb4d4e0 Compare June 28, 2026 20:04
@Shuu-37 Shuu-37 marked this pull request as ready for review June 29, 2026 04:45
next() already returns false on an empty result.
@Shuu-37 Shuu-37 force-pushed the feat/launch-token-upstream branch from 0d56f77 to 15d0d3f Compare June 29, 2026 04:47
@zach2good

Copy link
Copy Markdown
Contributor

For context, and what I've agreed with @WinterSolstice8:

If we were to take on a feature like this into xi_connect and xiloader, it would be a dead code path that we have to maintain for the sole benefit of a single downstream server (not going to happen). Or, we'd have to make/have submitted a small example launcher or process that fills in all the other things that would be needed to make this relevant (also a thing we don't have the time and resources for).

I'm firmly of the opinion that this doesn't benefit the codebase in any way and that everyone's time is much better spent literally anywhere else.

That being said. This has come up a few times, and I don't think it's going to go away, so we've come up with a design that will let downstream do whatever it is that they think is important, so we can get on with actually writing and maintaining the emulator.

xiloader changes

We already have a secure TLS connection between xiloader and xi_connect, through which we send various json payloads. We have some verification and logical steps inside xiloader, but there's no reason we can't just send every command line arg and other information to xi_connect and have the validation happen there.

This would allow us to validate:

  • Username
  • Password, or login token
  • OTP code (if needed)
  • Version, and all the other metadata that we might send from xiloader (I think there's a unique token per instance of xiloader too?)
  • Login command action (create, attempt, change pw, etc.)
  • Any other freeform argument: xiloader --example valueString would parse and be sent as ["example"] = "valueString", and then xi_connect can deal with it... however

Entirely on the server, rather than checking a couple of small bits locally. This also means that we pretty much will never have to update xiloader again.

xi_connect changes

We currently have a big switch statement that handles all the different commands as they come in. I'm proposing we use the CppModule design to inject a very restricted customisation point here. Or two.

auto onXiLoaderCreate(Request) -> Response;
auto onXiLoaderLogin(Request) -> Response;

Which can be customised. The Request will include the session information (already validated at this point), and the JSON payload. Then it'll be up to the implementer to respond to their desired arguments. If you then want to use jwt-cpp to decode and verify JWT tokens passed in with --token, that's up to you. If you want to send a deferred job to the scheduler to do a db write or an HTTP request, that's up to you. We don't have to support you.

Let it be known that I hate this, and it's the last time I'm ever going to touch anything related to modules in any form ever again.

@zach2good zach2good closed this Jun 30, 2026
@LandSandBoat LandSandBoat locked and limited conversation to collaborators Jun 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants